package com.ihr360.job.core.job.builder;

import com.ihr360.job.core.Job;
import com.ihr360.job.core.JobExecutionListener;
import com.ihr360.job.core.JobParametersIncrementer;
import com.ihr360.job.core.JobParametersValidator;
import com.ihr360.job.core.job.AbstractJob;
import com.ihr360.job.core.repository.JobRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

/**
 * A base class and utility for other job builders providing access to common properties like job repository.
 *
 * @author Dave Syer
 *
 * @since 2.2
 */
public abstract class JobBuilderHelper<B extends JobBuilderHelper<B>>  {
    protected final Logger logger = LoggerFactory.getLogger(JobBuilderHelper.class.getName());

    private final CommonJobProperties properties;

    public JobBuilderHelper(String name) {
        this.properties = new CommonJobProperties();
        properties.name = name;
    }
    /**
     * Create a new builder initialized with any properties in the parent. The parent is copied, so it can be re-used.
     *
     * @param parent a parent helper containing common step properties
     */
    protected JobBuilderHelper(JobBuilderHelper<?> parent) {
        this.properties = new CommonJobProperties(parent.properties);
    }

    /**
     * Add a job parameters validator.
     *
     * @param jobParametersValidator a job parameters validator
     * @return this to enable fluent chaining
     */
    public B validator(JobParametersValidator jobParametersValidator) {
        properties.jobParametersValidator = jobParametersValidator;
        @SuppressWarnings("unchecked")
        B result = (B) this;
        return result;
    }

    /**
     * Add a job parameters incrementer.
     *
     * @param jobParametersIncrementer a job parameters incrementer
     * @return this to enable fluent chaining
     */
    public B incrementer(JobParametersIncrementer jobParametersIncrementer) {
        properties.jobParametersIncrementer = jobParametersIncrementer;
        @SuppressWarnings("unchecked")
        B result = (B) this;
        return result;
    }

    /**
     * Sets the job repository for the job.
     *
     * @param jobRepository the job repository (mandatory)
     * @return this to enable fluent chaining
     */
    public B repository(JobRepository jobRepository) {
        properties.jobRepository = jobRepository;
        @SuppressWarnings("unchecked")
        B result = (B) this;
        return result;
    }

    /**
     * Register a job execution listener.
     *
     * @param listener a job execution listener
     * @return this to enable fluent chaining
     */
    public B listener(JobExecutionListener listener) {
        properties.addJobExecutionListener(listener);
        @SuppressWarnings("unchecked")
        B result = (B) this;
        return result;
    }

    /**
     * Set a flag to prevent restart an execution of this job even if it has failed.
     *
     * @return this to enable fluent chaining
     */
    public B preventRestart() {
        properties.restartable = false;
        @SuppressWarnings("unchecked")
        B result = (B) this;
        return result;
    }

    protected String getName() {
        return properties.name;
    }

    protected JobRepository getJobRepository() {
        return properties.jobRepository;
    }

    protected boolean isRestartable() {
        return properties.restartable;
    }

    protected void enhance(Job target) {

        if (target instanceof AbstractJob) {

            AbstractJob job = (AbstractJob) target;
            job.setJobRepository(properties.getJobRepository());

            JobParametersIncrementer jobParametersIncrementer = properties.getJobParametersIncrementer();
            if (jobParametersIncrementer != null) {
                job.setJobParametersIncrementer(jobParametersIncrementer);
            }
            JobParametersValidator jobParametersValidator = properties.getJobParametersValidator();
            if (jobParametersValidator != null) {
                job.setJobParametersValidator(jobParametersValidator);
            }

            Boolean restartable = properties.getRestartable();
            if (restartable != null) {
                job.setRestartable(restartable);
            }

            List<JobExecutionListener> listeners = properties.getJobExecutionListeners();
            if (!listeners.isEmpty()) {
                job.setJobExecutionListeners(listeners.toArray(new JobExecutionListener[0]));
            }

        }

    }

    public static class CommonJobProperties {

        private Set<JobExecutionListener> jobExecutionListeners = new LinkedHashSet<JobExecutionListener>();

        private boolean restartable = true;

        private JobRepository jobRepository;

        private JobParametersIncrementer jobParametersIncrementer;

        private JobParametersValidator jobParametersValidator;

        public CommonJobProperties() {
        }

        public CommonJobProperties(CommonJobProperties properties) {
            this.name = properties.name;
            this.restartable = properties.restartable;
            this.jobRepository = properties.jobRepository;
            this.jobExecutionListeners = new LinkedHashSet<JobExecutionListener>(properties.jobExecutionListeners);
            this.jobParametersIncrementer = properties.jobParametersIncrementer;
            this.jobParametersValidator = properties.jobParametersValidator;
        }

        public JobParametersIncrementer getJobParametersIncrementer() {
            return jobParametersIncrementer;
        }

        public void setJobParametersIncrementer(JobParametersIncrementer jobParametersIncrementer) {
            this.jobParametersIncrementer = jobParametersIncrementer;
        }

        public JobParametersValidator getJobParametersValidator() {
            return jobParametersValidator;
        }

        public void setJobParametersValidator(JobParametersValidator jobParametersValidator) {
            this.jobParametersValidator = jobParametersValidator;
        }

        public JobRepository getJobRepository() {
            return jobRepository;
        }

        public void setJobRepository(JobRepository jobRepository) {
            this.jobRepository = jobRepository;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public List<JobExecutionListener> getJobExecutionListeners() {
            return new ArrayList<JobExecutionListener>(jobExecutionListeners);
        }

        public void addStepExecutionListeners(List<JobExecutionListener> jobExecutionListeners) {
            this.jobExecutionListeners.addAll(jobExecutionListeners);
        }

        public void addJobExecutionListener(JobExecutionListener jobExecutionListener) {
            this.jobExecutionListeners.add(jobExecutionListener);
        }

        public boolean getRestartable() {
            return restartable;
        }

        public void setRestartable(boolean restartable) {
            this.restartable = restartable;
        }

        private String name;

    }
}